#ifndef TDGT_PL_H
#define TDGT_PL_H

#include <vector>
#include <utility>
#include <cmath>
#include <memory>
#include <initializer_list>
#include "Segment.h"
#include "cfloat"
#include "Constants.h"

using namespace std;


struct PLF {
    typedef shared_ptr<vector<Segment>> FPtr;

    typedef vector<Segment>::size_type size_type;

    FPtr f;

    float min_value, max_value;

    PLF() : f(make_shared<vector<Segment>>()) {}

    PLF(int sectionNum, float min_value, float delta) {
        this->min_value = DBL_MAX;
        this->max_value = DBL_MIN;
        f = selectGeneratePLF(sectionNum, min_value, delta).f;
    }

    PLF(initializer_list<Segment> segList) : f(make_shared<vector<Segment>>(segList)) {}

    PLF(const PLF& other) {
        if (other.f)
            f = make_shared<vector<Segment>>(*other.f);
    }

    PLF(PLF&& rhs) noexcept : f(std::move(rhs.f)) {}

    PLF& operator=(const PLF& other) {
        if (other.f)
            f = make_shared<vector<Segment>>(*other.f);
        return *this;
    }

    PLF& operator=(PLF&& rhs) noexcept {
        f = rhs.f;
        return *this;
    }

    Segment& operator[](size_type id) {
        return (*f)[id];
    }

    friend ostream& operator<<(ostream& out, const PLF& plf) {
        for (auto& e : *plf.f)
            out << e << " ";
        return out;
    }

    vector<Segment>::iterator dpt2seg(float t) {
        auto beg1 = (*f).begin();
        auto beg = (*f).begin();
        auto end = (*f).end();
        auto mid = beg + (end - beg) / 2;

        while (mid != end) {
            if (ge(t, mid->t))
                beg = mid + 1;
            else   
                end = mid;
            mid = beg + (end - beg) / 2;
        }
        return mid - 1;
    }
    inline float dpt2wgt(float t, vector<Segment>::iterator s1) {  
        auto s2 = s1 + 1;
        if (s2 != f->end()) {
            assert(lt(s1->t, s2->t) && ge(t, s1->t) && le(t, s2->t));
            float k = (s2->w - s1->w) / (s2->t - s1->t);
            assert(gt(k, -1)); 
            return k * (t - s1->t) + s1->w;
        }
        else { 
            assert(ge(t, s1->t));
            return s1->w;
        }
    }

    inline float dpt2wgt(float t) {
        auto pseg = dpt2seg(t);
        return dpt2wgt(t, pseg);
    }

    inline float arr2dpt(float arr, std::vector<Segment>::iterator s1) {
        auto s2 = s1 + 1;
        if (s2 != f->end()) {
            assert(lt(s1->t, s2->t) && ge(arr, s1->t + s1->w) && le(arr, s2->t + s2->w));
            float k = (s2->w - s1->w) / (s2->t - s1->t);
            assert(gt(k, -1));
            float arr1 = s1->w + s1->t;
            return s1->t + (arr - arr1) / (k + 1);
        }
        else {
            assert(ge(arr, s1->t + s1->w));
            return arr - s1->w;
        }
    }

    inline float dpt2arr(float t, std::vector<Segment>::iterator s1) {
        auto s2 = s1 + 1;
        if (s2 != f->end()) {
            assert(lt(s1->t, s2->t) && ge(t, s1->t) && le(t, s2->t));  
            float k = (s2->w - s1->w) / (s2->t - s1->t);
            assert(gt(k, -1)); 
            return k * (t - s1->t) + s1->w + t;
        }
        else {  
            assert(ge(t, s1->t));
            return s1->w + t;
        }
    }

    inline float dpt2arr(float td) {
        auto pseg = dpt2seg(td);
        return dpt2arr(td, pseg);
    }

    inline void push(float& pre_k, float dpt, float wgt, int intv) {
        auto& segs = *f;
        assert(le(dpt, TMAX));
        assert(le(wgt, INT_MAX));
        assert(gt(wgt, 0));
        if (segs.empty())
            segs.emplace_back(dpt, wgt, intv);
        else if (neq(segs.back().t, dpt)) {

            float new_k = (wgt - segs.back().w) / (dpt - segs.back().t);

            assert(gt(dpt, segs.back().t));
            assert(gt(new_k, -1));
            if (eq(pre_k, new_k)) {
                segs.back().t = dpt;
                segs.back().w = wgt;
                segs.back().intv = intv;
            }
            else {
                segs.emplace_back(dpt, wgt, intv);
                pre_k = new_k;
            }
        }
    }

    void compound(float ts, float te, PLF& PLF0, PLF& PLFc, int intv) {
        float pre_k = INT_MAX;
        assert(eq(PLF0.f->front().t, ts));
        auto pw0 = PLF0.f->begin();
        float dpt = pw0->t;
        float wgt0 = pw0->w;
        float arr0 = dpt + wgt0;
        auto pw1 = dpt2seg(arr0);
        float wgt1 = dpt2wgt(arr0, pw1);
        PLFc.push(pre_k, dpt, wgt0 + wgt1, intv);
        while (true) {
            bool valid0 = pw0 + 1 < PLF0.f->end();
            bool valid1 = pw1 + 1 < f->end();
            int turn; 
            if (valid0 && valid1) {
                float next_arr0 = (pw0 + 1)->t + (pw0 + 1)->w;
                if (lt((pw1 + 1)->t, next_arr0)) turn = 1;
                else turn = 0;
            }
            else if (valid1) turn = 1;
            else if (valid0) turn = 0;
            else break;
            if (turn) {
                pw1++;
                dpt = PLF0.arr2dpt(pw1->t, pw0);
                if (valid0) assert(lt(dpt, (pw0 + 1)->t));
                wgt0 = pw1->t - dpt;
                wgt1 = pw1->w;
            }
            else {
                pw0++;
                dpt = pw0->t;
                wgt0 = pw0->w;
                arr0 = dpt + wgt0;
                wgt1 = dpt2wgt(arr0, pw1);
            }
            PLFc.push(pre_k, dpt, wgt0 + wgt1, intv);
            if (ge(dpt, te))
                break;
        }
    }


    void compound(PLF& PLF0, PLF& PLFc, int intv) {
        float pre_k = INT_MAX;
        auto pw0 = PLF0.f->begin();
        float dpt = pw0->t; 
        float wgt0 = pw0->w;
        float arr0 = dpt + wgt0;
        auto pw1 = dpt2seg(arr0);
        float wgt1 = dpt2wgt(arr0, pw1);
        PLFc.push(pre_k, dpt, wgt0 + wgt1, intv);
        while (true) {
            bool valid0 = pw0 + 1 < PLF0.f->end();
            bool valid1 = pw1 + 1 < f->end();
            int turn; 
            if (valid0 && valid1) {
                float next_arr0 = (pw0 + 1)->t + (pw0 + 1)->w;
                if (lt((pw1 + 1)->t, next_arr0)) turn = 1;
                else turn = 0;
            }
            else if (valid1) turn = 1;
            else if (valid0) turn = 0;
            else break;
            if (turn) {
                pw1++;
                dpt = PLF0.arr2dpt(pw1->t, pw0);
                if (valid0) assert(lt(dpt, (pw0 + 1)->t));
                wgt0 = pw1->t - dpt;
                wgt1 = pw1->w;
            }
            else {
                pw0++;
                dpt = pw0->t;
                wgt0 = pw0->w;
                arr0 = dpt + wgt0;
                wgt1 = dpt2wgt(arr0, pw1);
            }
            PLFc.push(pre_k, dpt, wgt0 + wgt1, intv);
        }
    }

    inline void minseg(float& pre_k, float t1, float t2, float f1, float f2,
        float g1, float g2, int intvf, int intvg) {
        assert(ge(t2, t1)); 
        if (eq(t1, t2)) return; 
        if (eq(f1, g1)) {
            if (le(f2, g2)) push(pre_k, t1, f1, intvf);
            else push(pre_k, t1, g1, intvg);
        }
        else {
            float delta1 = g1 - f1;
            float delta2 = f2 - g2;
            int flag_cross = 0;
            if (gt(delta1, 0)) {
                push(pre_k, t1, f1, intvf);
                if (gt(delta2, 0)) flag_cross = -1; 
            }
            else {
                push(pre_k, t1, g1, intvg);
                if (lt(delta2, 0)) flag_cross = 1;
            }
            if (flag_cross) {
                float denominator = delta1 + delta2;
                float inter_t = (delta1 * t2 + delta2 * t1) / denominator;
                float inter_w = (delta1 * g2 + delta2 * g1) / denominator;
                if (flag_cross == 1) push(pre_k, inter_t, inter_w, intvf);
                else push(pre_k, inter_t, inter_w, intvg);
            }
        }
    }

    void minimize(PLF& PLFc) {
        float pre_k = INT_MAX;
        assert(!PLFc.f->empty());
        PLF PL_new;
        auto pw0 = f->begin(), pwc = PLFc.f->begin();
        assert(pw0->t == pwc->t);
        float t1 = pw0->t, t2, f1 = pw0->w, f2, g1 = pwc->w, g2;
        while (true) {
            int turn; 
            bool valid0 = pw0 + 1 < f->end();
            bool valid1 = pwc + 1 < PLFc.f->end();
            if (valid0 && valid1) {
                if (lt((pw0 + 1)->t, (pwc + 1)->t)) turn = 0;
                else turn = 1;
            }
            else if (valid0) turn = 0;
            else if (valid1) turn = 1;
            else break;
            if (turn) { 
                t2 = (pwc + 1)->t;
                f2 = dpt2wgt(t2, pw0);
                g2 = (pwc + 1)->w;
            }
            else {
                t2 = (pw0 + 1)->t;
                f2 = (pw0 + 1)->w;
                g2 = PLFc.dpt2wgt(t2, pwc);
            }
            PL_new.minseg(pre_k, t1, t2, f1, f2, g1, g2,
                pw0->intv, pwc->intv);
            assert(lt(t2, TMAX));
            t1 = t2;
            f1 = f2;
            g1 = g2;
            if (turn) pwc++; else pw0++;
        }
        PL_new.minseg(pre_k, t1, TMAX, f1, pw0->w, g1, pwc->w, 
            pw0->intv, pwc->intv);
        f = PL_new.f;
    }

    bool min4sync(PLF& PLFf, int inter_node_id) {
        bool changed = false;
        float pre_k = INT_MAX;
        assert(!PLFf.f->empty());
        PLF PL_new;
        auto pw0 = f->begin(), pwc = PLFf.f->begin();
        assert(pw0->t == pwc->t);
        float t1 = pw0->t, t2, f1 = pw0->w, f2, g1 = pwc->w, g2;
        while (true) {
            int turn; 
            bool valid0 = pw0 + 1 < f->end();
            bool valid1 = pwc + 1 < PLFf.f->end();
            if (valid0 && valid1) {
                if (lt((pw0 + 1)->t, (pwc + 1)->t)) turn = 0;
                else turn = 1;
            }
            else if (valid0) turn = 0;
            else if (valid1) turn = 1;
            else break;
            if (turn) {
                t2 = (pwc + 1)->t;
                f2 = dpt2wgt(t2, pw0);
                g2 = (pwc + 1)->w;
            }
            else { 
                t2 = (pw0 + 1)->t;
                f2 = (pw0 + 1)->w;
                g2 = PLFf.dpt2wgt(t2, pwc);
            }
            if (gt(f1, g1))
                changed = true;
            PL_new.minseg(pre_k, t1, t2, f1, f2, g1, g2,
                pw0->intv, inter_node_id);
            assert(lt(t2, TMAX));
            t1 = t2;
            f1 = f2;
            g1 = g2;
            if (turn) pwc++; else pw0++;
        }
        if (gt(f1, g1))
            changed = true;
        PL_new.minseg(pre_k, t1, TMAX, f1, pw0->w, g1, pwc->w, 
            pw0->intv, inter_node_id);
        f = PL_new.f;
        return changed;
    }

    std::shared_ptr<std::vector<Segment>> getSlice(float ts, float te) {
        auto ps = dpt2seg(ts), pe = dpt2seg(te);
        auto ans = std::make_shared<std::vector<Segment>>();
        ans->emplace_back(ts, dpt2wgt(ts, ps));
        if (ps == pe) 
            return ans;
        for (auto p = ps + 1; p <= pe; p++) {
            ans->push_back(*p);
        }
        return ans;
    }

    void set_pre_vexs(int marker) {
        for (auto& seg : *f)
            seg.intv = marker;
    }

    void set_pre_nodes(int nega_node_id) {
        for (auto& seg : *f)
            if (seg.intv > 0)
                seg.intv = nega_node_id;
    }

    PLF selectGeneratePLF(int sectionNum, float min_value, float delta) {
        PLF plf;
        switch (sectionNum) {
        case 1:
            plf = generate1seg(min_value, delta);
            break;
        case 4:
            plf = generate4seg(min_value, delta);
            break;
        case 7:
            plf = generate7seg(min_value, delta);
            break;
        default:
            break;
        }
        return plf;
    }

    PLF generate7seg(float min_value, float delta) {
        PLF plf;
        auto f = std::make_shared<std::vector<Segment>>(7); 
        int temp;
        int temp_delta = ceil(delta);

        float first = rand() % temp_delta;
        float second = 2 * delta - first;
        float time = rand() % (80000 - (int)first + 2) + first;
        (*f)[0] = { 0, first, INTV_CNTED };
        (*f)[1] = { time, second , INTV_CNTED };
        getMax_Min_PLF(first, second);

        float first1 = rand() % temp_delta;
        float second1 = 2 * delta - first1;
        temp = time + second;
        float ftime1 = rand() % (80000 - temp + 1) + temp;
        temp = ftime1 + first1;
        float stime1 = rand() % (80000 - temp + 1) + temp;
        (*f)[2] = { ftime1 , first1 , INTV_CNTED };
        (*f)[3] = { stime1 , second1 , INTV_CNTED };
        getMax_Min_PLF(first1, second1);

        float first2 = rand() % temp_delta;
        float second2 = 2 * delta - first2;
        temp = stime1 + second1;
        float ftime2 = rand() % (80000 - temp + 1) + temp;
        temp = ftime2 + first2;
        float stime2 = rand() % (80000 - temp + 1) + temp;
        (*f)[4] = { ftime2 , first2 , INTV_CNTED };
        (*f)[5] = { stime2 , second2 , INTV_CNTED };
        getMax_Min_PLF(first2, second2);

        (*f)[6] = { 85000 , delta , INTV_CNTED };

        plf.f = f;
        return plf;

    }

    PLF generate4seg(float min_value, float delta) {
        auto f = std::make_shared<std::vector<Segment>>(4); 
        int temp;
        int temp_delta = ceil(delta);

        float first = rand() % temp_delta;
        float second = 2 * delta - first;
        float time = rand() % (86400 - (int)first + 2) + first;
        (*f)[0] = { 0, first, INTV_CNTED };
        (*f)[1] = { time, second , INTV_CNTED };
        getMax_Min_PLF(first, second);

        float first1 = rand() % temp_delta;
        float second1 = 2 * delta - first1;
        temp = time + second;
        float ftime1 = rand() % (86400 - temp + 2) + temp;
        temp = ftime1 + first1;
        float stime1 = rand() % (86400 - temp + 2) + temp;
        (*f)[2] = { ftime1 , first1 , INTV_CNTED };
        (*f)[3] = { stime1 , second1 , INTV_CNTED };
        getMax_Min_PLF(first1, second1);

        PLF plf;
        plf.f = f;
        return plf;
    }

    PLF generate1seg(float min_value, float delta) {
        auto f = std::make_shared<std::vector<Segment>>(1); 
        (*f)[0] = { 0, delta, INTV_CNTED };
        min_value = max_value = delta;
        PLF plf;
        plf.f = f;
        return plf;
    }

    void getMax_Min_PLF(float first, float second) {
        min_value = min(min_value, min(first, second));
        max_value = max(max_value, max(first, second));
    }


};


#endif //TDGT_PL_H


